﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using LaTeXGrapher;


namespace LaTeXGrapherTests
{
    [TestFixture]
    public class ExpressionTests
    {
        [Test]
        public void SimpleExpressions()
        {
            TestConstantExpression("1 + 2 * 3", 1.0 + 2.0 * 3.0);
            TestConstantExpression("1 + 2 3", 1.0 + 2.0 * 3.0);
            TestConstantExpression("1 * 2 / 4", 1.0 * 2.0 / 4.0);
            TestConstantExpression("1 - 2 * 3", 1.0 - 2.0 * 3.0);
            TestConstantExpression("1 * 2 + 4", 1.0 * 2.0 + 4.0);
            TestConstantExpression("1 * -2 + 4", 1.0 * -2.0 + 4.0);

            TestConstantExpression("4^2", Math.Pow(4.0, 2.0));
            TestConstantExpression("4^2+2", Math.Pow(4.0, 2.0) + 2.0);
        }

        [Test]
        public void DecimalNumbers()
        {
            TestConstantExpression("1.2", 1.2);
            TestConstantExpression("1.2 * -2.3 + 4.4", 1.2 * -2.3 + 4.4);

            TestConstantExpression("1.5^1.6^1.2 + 1", Math.Pow(1.5, Math.Pow(1.6, 1.2)) + 1);
        }

        [Test]
        public void ScientificNotation()
        {
            TestConstantExpression("1e-2", 1e-2);
            TestConstantExpression("1e2", 1e2);
            TestConstantExpression("1e+2", 1e+2);
            TestConstantExpression("1E-2", 1E-2);
            TestConstantExpression("1E2", 1E2);
            TestConstantExpression("1E+2", 1E+2);

            TestConstantExpression("1.0e2", 1.0e2);
            TestConstantExpression("1.01e+2", 1.01e+2);
            TestConstantExpression("1.10E-2", 1.10E-2);
            //TestConstantExpression(".10E2", .10E2); // Unlike C# our numbers must start with a number.

            TestConstantExpression("1e2 * -2.3e-10 + 4.4", 1e2 * -2.3e-10 + 4.4);
        }

        [Test]
        public void Constants()
        {
            TestConstantExpression("e", Math.E);
            TestConstantExpression("pi", Math.PI);
            TestConstantExpression("NaN", double.NaN);
        }

        [Test]
        public void BuiltinFunctions()
        {
            TestConstantExpression("sin(1)", Math.Sin(1));
            TestConstantExpression("cos(1)", Math.Cos(1));
            TestConstantExpression("tan(1)", Math.Tan(1));
            TestConstantExpression("abs(1)", Math.Abs(1));
            TestConstantExpression("abs(-1)", Math.Abs(-1));
            TestConstantExpression("arctan(1)", Math.Atan(1));
            TestConstantExpression("arcsin(1)", Math.Asin(1));
            TestConstantExpression("arccos(1)", Math.Acos(1));
            TestConstantExpression("floor(1.5)", Math.Floor(1.5));
            TestConstantExpression("ceiling(1.5)", Math.Ceiling(1.5));
        
            TestConstantExpression("ln(1)", Math.Log(1));
            TestConstantExpression("log(1)", Math.Log10(1));

            TestConditionalExpression("odd(1)", MathHelpers.Odd(1));
            TestConditionalExpression("even(1)", MathHelpers.Even(1));
        }

        [Test]
        public void NestedFunctions()
        {
            TestConstantExpression("ln(abs(-1) + sin(1))", Math.Log(Math.Abs(-1) + Math.Sin(1)));
        }

        [Test]
        public void LogicOperations()
        {
            TestConditionalExpression("0", false);
            TestConditionalExpression("1", true);
            TestConditionalExpression("!0", !false);
            TestConditionalExpression("!1", !true);

            TestConditionalExpression("1 < 2", 1 < 2);
            TestConditionalExpression("1 > 2", 1 > 2);
            TestConditionalExpression("1 = 2", 1 == 2);
            TestConditionalExpression("1 == 1", 1 == 1);
            TestConditionalExpression("1 <= 2", 1 <= 2);
            TestConditionalExpression("2 <= 1", 2 <= 1);
            TestConditionalExpression("1 >= 2", 1 >= 2);
            TestConditionalExpression("1 >= 1", 1 >= 1);
            TestConditionalExpression("2 >= 1", 2 >= 1);
            TestConditionalExpression("1 != 2", 1 != 2);
            TestConditionalExpression("1 != 1", 1 != 1);
            TestConditionalExpression("1 | 0", true || false);
            TestConditionalExpression("1 || 0", true || false);
            TestConditionalExpression("1 & 0", true && false);
            TestConditionalExpression("1 && 0", true && false);

            TestConditionalExpression("1 < 2 - 10", 1 < 2 - 10);
        }

        [Test]
        public void Grouping()
        {
            TestConstantExpression("(((1)))", 1.0);
            TestConstantExpression("(1 + 2) * 3", (1.0 + 2.0) * 3.0);
            TestConstantExpression("1 + (2 * 3)", 1.0 + (2.0 * 3.0));
            TestConstantExpression("((1 * 2) / 4)", ((1.0 * 2.0) / 4.0));
            TestConstantExpression("(1 - 2) * 3", (1.0 - 2.0) * 3.0);
            TestConstantExpression("(1 * 2) + 4", (1.0 * 2.0) + 4.0);
            TestConstantExpression("1 * (-2) + 4", 1.0 * (-2.0) + 4.0);
        }

        [Test]
        public void Multiplication()
        {
            //TestConstantExpression("(1 + 3)(4 - 6)", (1.0 + 3.0) * (4.0 - 6.0)); // not worried about supporting this for now.
        }

        public void TestConditionalExpression(string expression, bool expected)
        {
            Assert.AreEqual(expected, ExpressionParser.EvaluateConditional(expression), "Conditional expression " + expression);
        }

        public void TestConstantExpression(string expression, double expected)
        {
            if (double.IsNaN(expected))
                Assert.IsTrue(double.IsNaN(ExpressionParser.Evaluate(expression)), "Expression " + expression + " expected NaN");
            else
                Assert.AreEqual(expected, ExpressionParser.Evaluate(expression), "Expression " + expression);
        }
    }
}
